﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Core;

namespace NHibernateGenerator
{
	public class NHibernateMappingFileGenerator
	{
	    private Type currentClassType;
	    private const string ID_MAPPING = @"		<id name=""Id"" column=""`id`"" type=""{TYPE}"">
			<generator class=""{GENERATOR_TYPE}"" />
		</id>";
		
		private const string MANY_TO_ONE_MAPPING = @"		<many-to-one name=""{NAME}"" column=""`{COLUMN}`"" class=""{FULL_CLASS_NAME}"" foreign-key=""{FOREIGN_KEY}""/>";
		
		private const string NHIBERNATE_DIRECTORY = @"GeneratedFiles\Model\Dao\NHibernate\";

		private const string PROPERTY_MAPPING = @"		<property name=""{NAME}"" column=""`{COLUMN}`"" type=""{TYPE}""/>";

		private const string VERSION_MAPPING = @"		<version name=""Version"" column=""`version`"" type=""{TYPE}"" />";

		private const string ILIST_MAPPING =
@"		<idbag name=""{NAME}"" table=""`{TABLE_NAME}`"" lazy=""true"">
			<collection-id column=""`id`"" type=""Int64"">
				<generator class=""hilo""/>
			</collection-id>
			<key column=""`{KEY_COLUMN}`"" foreign-key=""{FK_KEY_COLUMN}""/>
			<many-to-many column=""`{OBJECT_COLUMN}`"" class=""{CLASS_NAME}"" foreign-key=""{FK_OBJECT_COLUMN}"" />
		</idbag>";

		public NHibernateMappingFileGenerator() {}


		public NHibernateMappingFileGenerator(Assembly assembly)
		{
			this.Assembly = assembly;
		}


		public Assembly Assembly { get; set; }


		public void Execute(IEnumerable<string> selectedClassNames)
		{
			//read in the template
			TextReader reader = new StreamReader(AppDomain.CurrentDomain.BaseDirectory + @"\TemplateFiles\NHibernateMappingFile.template");

			string template = reader.ReadToEnd();

			TextWriter writer;

			foreach (string fullName in selectedClassNames) {
				//create a new template string
				string classTemplate = template;

				this.currentClassType = this.Assembly.GetType(fullName);//.GetTypeByClassName(className);

				string className = currentClassType.Name;
			    string assemblyName = this.Assembly.GetName().Name;
				string nameSpace = currentClassType.Namespace;
				string tableNameForClass = className.ToLower();

				classTemplate = classTemplate.Replace("{ASSEMBLY}", assemblyName);
				classTemplate = classTemplate.Replace("{CLASS_NAME}", className);
				classTemplate = classTemplate.Replace("{NAMESPACE}", nameSpace);
				classTemplate = classTemplate.Replace("{TABLE_NAME_FOR_CLASS}", tableNameForClass);

				classTemplate = this.AddProperties(classTemplate, currentClassType);

				Directory.CreateDirectory(AppDomain.CurrentDomain.BaseDirectory + NHIBERNATE_DIRECTORY);

				writer = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + NHIBERNATE_DIRECTORY + className + ".hbm.xml");

				writer.Write(classTemplate);

				writer.Flush();
				writer.Close();
			}
		}


		private string AddProperties(string classTemplate,
		                             Type classType)
		{
			string propertiesValue = "";

			//process the ID
			propertiesValue = this.CreateIdNode(classType.GetMember("Id")[0]) + Environment.NewLine;

			//process the version
			propertiesValue = propertiesValue + this.CreateVersionNode(classType.GetMember("Version")[0]) + Environment.NewLine;

			//process the properties
			propertiesValue = propertiesValue + this.CreateProperties(classType.GetMembers()) + Environment.NewLine;

			return classTemplate.Replace("{PROPERTIES}", propertiesValue);
		}


		private string CreateProperties(MemberInfo[] members)
		{
			string properties = "";

			foreach (MemberInfo member in members) {
				if(member.Name != "Id" && member.Name != "Version") {
					properties += this.GetPropertyMapping(member);
				}
			}

			return properties;
		}


		private string GetPropertyMapping(MemberInfo member)
		{
			string propertyMapping = "";

			if(member.MemberType == MemberTypes.Property) {
				Type typeOfMember = Utilities.GetRealTypeFromMemberProperty(member);
				
				if(Utilities.IsPrimitive(typeOfMember)) {
					
					propertyMapping = this.ProcessPrimitiveTypeProperty(member) + Environment.NewLine;
				} else if(typeOfMember.Name == typeof(IList<object>).Name) {
					//propertyMapping += "<!-- ILIST NOT CURRENTLY SUPPORTED FOR " + member.Name + " " + typeOfMember + "-->" + Environment.NewLine;

					propertyMapping += this.ProcessIList(member) + Environment.NewLine;

				} else if(typeOfMember.Name == typeof(IDictionary<object, object>).Name) {
					propertyMapping += "<!-- IDICTIONARY NOT CURRENTLY SUPPORTED FOR " + member.Name + " " + typeOfMember + "-->" + Environment.NewLine;
				} else {
					//treat as a many to many
					propertyMapping += this.ProcessManyToOne(member) + Environment.NewLine;
				}

				//propertyMapping = propertyMapping + "Member type is " + typeOfMember.Name + Environment.NewLine;

			    propertyMapping += Environment.NewLine;

			}

			return propertyMapping;
		}

		/// <summary>
		/// <idbag name=""{NAME}"" table=""{TABLE_NAME}"" lazy=""true"">
		///		<collection-id column=""id"" type=""Int64"">
		///			<generator class=""hilo""/>
		///		</collection-id>
		///		<key column=""{KEY_COLUMN}"" foreign-key=""{FK_KEY_COLUMN}""/>
        ///		<many-to-many column=""{OBJECT_COLUMN}"" class=""{CLASS_NAME}"" foreign-key=""{FK_OBJECT_COLUMN}"" />
		/// </idbag>
		/// </summary>
		/// <param name="member"></param>
		/// <returns></returns>
		private string ProcessIList(MemberInfo member)
		{
			string listMapping = "";

			//Type memberType = this.GetRealTypeFromMemberProperty(member);

			Type memberType = ((PropertyInfo) member).PropertyType.GetGenericArguments()[0];

			listMapping = ILIST_MAPPING.Replace("{NAME}", member.Name);

			listMapping = listMapping.Replace("{TABLE_NAME}", this.currentClassType.Name.ToLower() + "_" + member.Name.ToLower() + "_" + memberType.Name.ToLower());

            listMapping = listMapping.Replace("{KEY_COLUMN}", this.currentClassType.Name.ToLower() + "_" + member.Name.ToLower() + "_id");

            listMapping = listMapping.Replace("{FK_KEY_COLUMN}", "FK_" + this.currentClassType.Name.ToUpper() + "_" + member.Name.ToUpper() + "_" + memberType.Name.ToUpper());

			listMapping = listMapping.Replace("{CLASS_NAME}", string.Format("{0}.{1}, {2}", memberType.Namespace,memberType.Name, memberType.Assembly.GetName().Name));

			listMapping = listMapping.Replace("{OBJECT_COLUMN}",  memberType.Name.ToLower() + "_id");

            listMapping = listMapping.Replace("{FK_OBJECT_COLUMN}", "FK_" + memberType.Name.ToUpper() + "_" + this.currentClassType.Name.ToUpper() + "_" + member.Name.ToUpper());
            
			return listMapping;
		
		}


		/// <summary>
		/// This is what it will generate
		/// <many-to-one name=""{NAME}"" column=""{COLUMN}"" class=""{FULL_CLASS_NAME}"" foreign-key=""{FOREIGN_KEY}""/>
		/// </summary>
		/// <param name="member"></param>
		/// <returns></returns>
		private string ProcessManyToOne(MemberInfo member)
		{
			string manyValue = "";

			Type memberType = Utilities.GetRealTypeFromMemberProperty(member);

			manyValue = MANY_TO_ONE_MAPPING.Replace("{NAME}", member.Name);

			manyValue = manyValue.Replace("{COLUMN}", member.Name.ToLower() + "_id");

			manyValue = manyValue.Replace("{FULL_CLASS_NAME}", string.Format("{0}.{1}, {2}", memberType.Namespace,memberType.Name, memberType.Assembly.GetName().Name));

			string foreignKey = string.Format("FK_{0}_{1}", member.Name.ToUpper(), member.DeclaringType.Name.ToUpper());

			manyValue = manyValue.Replace("{FOREIGN_KEY}", foreignKey);

			return manyValue;
		}


		/// <summary>
		/// generates this: 
		/// 	<property name=""{NAME}"" column=""{COLUMN}"" type=""{TYPE}""/>";
		/// </summary>
		/// <param name="member"></param>
		/// <returns></returns>
		private string ProcessPrimitiveTypeProperty(MemberInfo member)
		{
			Type memberType = Utilities.GetRealTypeFromMemberProperty(member);

			string propertyValue = PROPERTY_MAPPING.Replace("{NAME}", member.Name);

			propertyValue = propertyValue.Replace("{COLUMN}", member.Name.ToLower());

			propertyValue = propertyValue.Replace("{TYPE}", memberType.Namespace + "." + memberType.Name);

			return propertyValue;
		}



		private string CreateIdNode(MemberInfo memberInfo)
		{
			string idNode = "";

			Type typeOfId = Utilities.GetRealTypeFromMemberProperty(memberInfo);

			idNode = ID_MAPPING.Replace("{TYPE}", typeOfId.Name);

			string generatorType = "native";

			if (typeOfId == typeof (Guid)) {
				generatorType = "guid";
			}

			idNode = idNode.Replace("{GENERATOR_TYPE}", generatorType);

			return idNode;
		}

		private string CreateVersionNode(MemberInfo memberInfo)
		{
			string versionNode = "";

			Type typeOfVersion = Utilities.GetRealType(((PropertyInfo)memberInfo).PropertyType);

			versionNode = VERSION_MAPPING.Replace("{TYPE}", typeOfVersion.Name);

			return versionNode;
		}
	}
}